/*******************************************************************************
 * Copyright (c) 2011 SYSTEMA GmbH.
 * All rights reserved.
 *
 * Contributors:
 *     SYSTEMA GmbH - initial API and implementation
 *******************************************************************************/
package de.sr.gwtdemo.client.widgets.console;
import java.util.Date;
import java.util.HashMap;

import com.google.gwt.i18n.client.DateTimeFormat;

/**
 * A wrapper class to log to the {@link Console}.
 * 
 * @author jhilde
 * @since 22.03.2011
 * @version $Revision$
 * 
 *          $LastChangedDate$ $LastChangedBy$
 */
public class UiLogger {

	public static class Formatter {

		public static final String EMPTY_STRING = "";

		public static final String NULL_STRING = "null";

		public static final String NEW_LINE = "\n";

		public static final String PROPERTY_LEVEL = "@level";

		public static final String PROPERTY_MESSAGE = "@message";

		public static final String PROPERTY_CLASS = "@class";

		public static final String PROPERTY_TIMESTAMP = "@timestamp";

		public static final String PROPERTY_EXCEPTION = "@exception";

		public static final String DEFAULT_TEMPLATE = "[@level] @class - @timestamp> @message@exception" + NEW_LINE;

		public static final String STACK_TRACE_ELEMENT_PREFIX = "\tat ";

		public static final String LEVEL_DEBUG = "D";

		public static final String LEVEL_ERROR = "E";

		public static final String LEVEL_INFO = "I";

		public static final String LEVEL_WARN = "W";

		public static final DateTimeFormat DEFAULT_TIMESTAMP = DateTimeFormat.getFormat("HH:mm:ss:SSS");

		protected void appendFormattedStackStraceElement(StringBuilder stringBuilder, StackTraceElement element) {
			if (element == null)
				return;

			stringBuilder.append(STACK_TRACE_ELEMENT_PREFIX);
			stringBuilder.append(element);
		}

		public String format(Level level, Class<?> clazz, String rawMessage, String exceptionString) {
			String message = getTemplate();
			if (message == null || message.length() == 0)
				return EMPTY_STRING;

			if (rawMessage == null)
				rawMessage = NULL_STRING;

			if (exceptionString == null)
				exceptionString = EMPTY_STRING;

			if (message.contains(PROPERTY_LEVEL)) {
				String replacement = formatLevel(level);
				message = message.replace(PROPERTY_LEVEL, replacement);
			}

			if (message.contains(PROPERTY_CLASS)) {
				String replacement = formatClass(clazz);
				message = message.replace(PROPERTY_CLASS, replacement);
			}

			if (message.contains(PROPERTY_MESSAGE)) {
				message = message.replace(PROPERTY_MESSAGE, rawMessage);
			}

			if (exceptionString != null && message.contains(PROPERTY_EXCEPTION)) {
				message = message.replace(PROPERTY_EXCEPTION, exceptionString);
			}

			if (message.contains(PROPERTY_TIMESTAMP)) {
				Date timestamp = new Date();
				String replacement = formatTimestamp(timestamp);
				message = message.replace(PROPERTY_TIMESTAMP, replacement);
			}

			return message;
		}

		public String format(Level level, Class<?> clazz, String rawMessage, Throwable exception) {
			String exceptionString = null;

			String message = getTemplate();
			if (message != null && message.length() != 0 && message.contains(PROPERTY_EXCEPTION)) {
				exceptionString = formatThrowable(exception);
			}

			return format(level, clazz, rawMessage, exceptionString);
		}

		protected String formatClass(Class<?> clazz) {
			return clazz.getName();
		}

		protected String formatLevel(Level level) {
			if (level == Level.DEBUG)
				return LEVEL_DEBUG;
			if (level == Level.ERROR)
				return LEVEL_ERROR;
			if (level == Level.INFO)
				return LEVEL_INFO;
			if (level == Level.WARN)
				return LEVEL_WARN;
			else
				return String.valueOf(level.name().charAt(0));
		}

		protected String formatThrowable(Throwable throwable) {
			if (throwable == null)
				return EMPTY_STRING;

			StringBuilder result = new StringBuilder();
			result.append(NEW_LINE);
			result.append(throwable);

			StackTraceElement[] elements = throwable.getStackTrace();
			for (int i = 0; i < elements.length; i++) {
				result.append(NEW_LINE);
				appendFormattedStackStraceElement(result, elements[i]);
			}

			return result.toString();
		}

		protected String formatTimestamp(Date timestamp) {
			return DEFAULT_TIMESTAMP.format(timestamp);
		}

		public String getTemplate() {
			return DEFAULT_TEMPLATE;
		}
	}

	public enum Level {
		DEBUG,
		ERROR,
		INFO,
		WARN
	}

	protected static final Formatter DEFAULT_FORMATTER = new Formatter();

	public static final HashMap<Level, Integer> LEVEL_PRIORITY = new HashMap<Level, Integer>();

	static {
		LEVEL_PRIORITY.put(Level.DEBUG, 1);
		LEVEL_PRIORITY.put(Level.INFO, 2);
		LEVEL_PRIORITY.put(Level.WARN, 3);
		LEVEL_PRIORITY.put(Level.ERROR, 4);
	}

	private Class<?> type = null;

	private static Level logLevel = Level.INFO;

	private Formatter formatter = DEFAULT_FORMATTER;

	private UiLogger(Class<?> type) {
		setType(type);
	}

	public static UiLogger create(Class<?> type) {
		if (type == null)
			type = Object.class;
		return new UiLogger(type);
	}

	public static Level getLogLevel() {
		return logLevel;
	}

	public static boolean isLogEnabled(Level levelToCheck) {
		if (levelToCheck == null)
			return true;

		Level level = getLogLevel();
		if (level == null)
			return false;

		Integer levelIndex = LEVEL_PRIORITY.get(level);
		Integer levelToCheckIndex = LEVEL_PRIORITY.get(levelToCheck);

		if (levelIndex != null && levelToCheckIndex != null) {
			return levelToCheckIndex >= levelIndex;
		}

		return false;
	}

	public static void setLogLevel(Level logLevel) {
		UiLogger.logLevel = logLevel;
	}

	public void debug(String message) {
		log(Level.DEBUG, message, (String) null);
	}

	public void debug(String message, String exceptionString) {
		log(Level.DEBUG, message, exceptionString);
	}

	public void debug(String message, Throwable exception) {
		log(Level.DEBUG, message, exception);
	}

	public void doLog(Level level, Class<?> clazz, String message) {
		doLog(level, clazz, message, (String) null);
	}

	protected void doLog(Level level, Class<?> clazz, String message, String exceptionString) {
		if (!isLogEnabled(level))
			return;

		Formatter formatter = getFormatter();
		if (formatter == null)
			return;

		String formattedMessage = formatter.format(level, clazz, message, exceptionString);
		boolean isError = level == Level.ERROR;
		Console.getInstance().log(formattedMessage, isError);
	}

	protected void doLog(Level level, Class<?> clazz, String message, Throwable exception) {
		if (!isLogEnabled(level))
			return;

		Formatter formatter = getFormatter();
		if (formatter == null)
			return;

		String formattedMessage = formatter.format(level, clazz, message, exception);
		boolean isError = level == Level.ERROR;
		Console.getInstance().log(formattedMessage, isError);
	}

	public void error(String message) {
		log(Level.ERROR, message, (String) null);
	}

	public void error(String message, String exceptionString) {
		log(Level.ERROR, message, exceptionString);
	}

	public void error(String message, Throwable exception) {
		log(Level.ERROR, message, exception);
	}

	protected Formatter getFormatter() {
		return formatter;
	}

	public Class<?> getType() {
		return type;
	}

	public void info(String message) {
		log(Level.INFO, message, (String) null);
	}

	public void info(String message, String exceptionString) {
		log(Level.INFO, message, exceptionString);
	}

	public void info(String message, Throwable exception) {
		log(Level.INFO, message, exception);
	}

	public void log(Level level, String message) {
		log(level, message, (String) null);
	}

	public void log(Level level, String message, String exceptionString) {
		doLog(level, getType(), message, exceptionString);
	}

	public void log(Level level, String message, Throwable exception) {
		doLog(level, getType(), message, exception);
	}

	protected void setFormatter(Formatter formatter) {
		this.formatter = formatter;
	}

	protected void setType(Class<?> clazz) {
		this.type = clazz;
	}

	public void warn(String message) {
		log(Level.WARN, message, (String) null);
	}

	public void warn(String message, String exceptionString) {
		log(Level.WARN, message, exceptionString);
	}

	public void warn(String message, Throwable exception) {
		log(Level.WARN, message, exception);
	}

}
